#include <gif_lib.h>
#include <stdio.h>
#include <setjmp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <string.h>
#include <stdlib.h>

#define FB_DEVICE_NAME "/dev/fb0"
int fd_fb;
struct fb_var_screeninfo var;	/* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;

typedef struct PictureData {
	FILE *ptFp;//文件句柄
	
	int iWidth;
	int iHeight;
	unsigned char *pucRgbData;

    /* use for GIF */
    struct GifFileType ptGifStrPoint;//gif结构体
	GifRowType *gpScreenBuffer;
	GifFileType *gpGifFile;
}T_PictureData, *PT_PictureData;

static int FBDeviceInit(void);
void lcd_put_pixel(int x, int y, unsigned int color);
static void FbShowPicture(PT_PictureData ptData);

/**********************************************************************
 * 函数名称： IsnotGif
 * 功能描述：判断是否为GIF文件
 * 输入参数： ppFp - 文件句柄指针
 					strFileName - 文件名
 * 返 回 值：0 - 是GIF格式 其他-不是GIF格式
 ***********************************************************************/
int IsnotGif(FILE **ppFp, const char *strFileName) 
{
	char strCheckHeader[3]; int ret = 0;
	*ppFp= fopen(strFileName, "rb");
	if (*ppFp== NULL) {
		return -1;
	}
	/* 读取GIF文件前3个字节，直接比较文件头即可判断是否为GIF格式 */
	if (fread(strCheckHeader, 1, 3, *ppFp) != 3) 
		return -1;
	
	if((strCheckHeader[0] != 0x47) || (strCheckHeader[1] != 0x49) || (strCheckHeader[2] != 0x46)) //文件前3个字节GIF
		return -2;

	return 0;
}

void GifFreeFile(PT_PictureData ptData)
{
    int32_t idx = 0;
    int32_t error = 0;

    for (idx = 0; idx < ptData->gpGifFile->SHeight; idx++)
    {
        if (NULL != ptData->gpScreenBuffer[idx])
        {
            free(ptData->gpScreenBuffer[idx]);
            ptData->gpScreenBuffer[idx] = NULL;
        }
    }

    if (NULL != ptData->gpScreenBuffer)
    {
       free(ptData->gpScreenBuffer);
       ptData->gpScreenBuffer = NULL;
    }

    if (NULL != ptData->gpGifFile)
    {
       DGifCloseFile(ptData->gpGifFile, &error);
       ptData->gpGifFile = NULL;
    }

	if(NULL != ptData->pucRgbData)
	{
		free(ptData->pucRgbData);
		ptData->pucRgbData = NULL;
	}
}

int DecodeGif2Rgb(const char *strFileName, PT_PictureData ptData)
{
	int error = 0;
    int size = 0;
    int idx = 0;
    int ret = 0;

	/* 0.判断该文件是否为PNG格式 */
	if(IsnotGif(&ptData->ptFp, strFileName)) {
		printf("file is not png ...\n");
		return -1;
	}

	do
    {
		ptData->gpGifFile = DGifOpenFileName(strFileName, &error);
        if (NULL == ptData->gpGifFile)
        {
            ret = -2;
            break;
        }

        if ((ptData->gpGifFile->SHeight == 0) || (ptData->gpGifFile->SWidth == 0))
        {
            ret = -3;
            break;
        }

        ptData->gpScreenBuffer = (GifRowType *)malloc(ptData->gpGifFile->SHeight * sizeof(GifRowType));
        if (NULL == ptData->gpScreenBuffer)
        {
            ret = -4;
            break;
        }

        /* Size in bytes one row */
        size = ptData->gpGifFile->SWidth * sizeof(GifPixelType); //一行多少像素点
        ptData->gpScreenBuffer[0] = (GifRowType)malloc(size);
        if(NULL == ptData->gpScreenBuffer[0])
        {
            ret = -5;
            break;
        }

        /* Set its color to BackGround */
        for (idx = 0; idx < ptData->gpGifFile->SWidth; idx++)
        {
            ptData->gpScreenBuffer[0][idx] = ptData->gpGifFile->SBackGroundColor;
        }

        /* Allocate the other rows, and set their color to background too */
        for (idx = 1; idx < ptData->gpGifFile->SHeight; idx++)
        {
            ptData->gpScreenBuffer[idx] = (GifRowType)malloc(size);
            if (NULL == ptData->gpScreenBuffer[idx])
            {
                ret = -6;
                break;
            }
            memcpy(ptData->gpScreenBuffer[idx], ptData->gpScreenBuffer[0], size);
        }

        if (0 > ret)
        {
            break;
        }
    } while (0);
	ptData->iHeight = ptData->gpGifFile->SHeight;
	ptData->iWidth = ptData->gpGifFile->SWidth;
	ptData->pucRgbData = malloc(ptData->iHeight * ptData->iWidth * 3);

    if (0 > ret)
    {
        GifFreeFile(ptData);
    }
    return ret;
}

int32_t GifFrameShow(PT_PictureData ptData)
{
    ColorMapObject *colorMap = NULL;
    GifByteType *extension = NULL;
	GifRecordType gRecordType = UNDEFINED_RECORD_TYPE;

    int32_t InterlacedOffset[] = { 0, 4, 2, 1 };  // The way Interlaced image should
    int32_t InterlacedJumps[] = { 8, 8, 4, 2 };   // be read - offsets and jumps...
    unsigned char rgbBuf[1000 * 480 * 3] = {0};

    int32_t extCode = 0;
    int32_t row = 0;
    int32_t col = 0;
    int32_t width = 0;
    int32_t height = 0;
    int32_t iW = 0;
    int32_t iH = 0;
    int32_t ret = 0;

    int i ,j, iPos;
    int iColor;
    GifColorType *ColorMapEntry = NULL;
    GifRowType GifRow = NULL;
    int32_t idxH = 0;
    int32_t idxW = 0;

    do
    {
        if (DGifGetRecordType(ptData->gpGifFile, &gRecordType) == GIF_ERROR)
        {
            ret = -1;
            break;
        }

        switch (gRecordType)
        {
            case IMAGE_DESC_RECORD_TYPE:
                if (DGifGetImageDesc(ptData->gpGifFile) == GIF_ERROR)
                {
                    ret = -2;
                    break;
                }

                row = ptData->gpGifFile->Image.Top;
                col = ptData->gpGifFile->Image.Left;
                width = ptData->gpGifFile->Image.Width;
                height = ptData->gpGifFile->Image.Height;

                if (ptData->gpGifFile->Image.Interlace)
                {
                    for (iH = 0; iH < 4; iH++)
                    {
                        for (iW = row + InterlacedOffset[iH];
                                iW < row + height; iW += InterlacedJumps[iH])
                        {
                            DGifGetLine(ptData->gpGifFile, &ptData->gpScreenBuffer[iW][col], width);
                        }
                    }
                }
                else
                {
                    for (iH = 0; iH < height; iH++)
                    {
                        DGifGetLine(ptData->gpGifFile, &ptData->gpScreenBuffer[row++][col], width);
                    }
                }

                colorMap = (ptData->gpGifFile->Image.ColorMap ?
                        ptData->gpGifFile->Image.ColorMap : ptData->gpGifFile->SColorMap);
                if (colorMap == NULL)
                {
                    ret = -3;
                    break;
                }

                // GifScreenBufferToRgb888(colorMap, rgbBuf, gpScreenBuffer,
                //        gpGifFile->SWidth, gpGifFile->SHeight);
                i = 0; iPos = 0;
				
                for (idxH = 0; idxH < ptData->gpGifFile->SHeight; idxH++)
                {
                    GifRow = ptData->gpScreenBuffer[idxH];

                    for (idxW = 0; idxW < ptData->gpGifFile->SWidth; idxW++)
                    {
                        ColorMapEntry = &colorMap->Colors[GifRow[idxW]];

						ptData->pucRgbData[iPos++] = ColorMapEntry->Red;
						ptData->pucRgbData[iPos++] = ColorMapEntry->Green;
						ptData->pucRgbData[iPos++] = ColorMapEntry->Blue;
                    }
                }

				FbShowPicture(ptData);

                break;

            case EXTENSION_RECORD_TYPE:
                /* Skip any extension blocks in file: */
                if (DGifGetExtension(ptData->gpGifFile, &extCode, &extension) == GIF_ERROR)
                {
                    ret = -4;
                    break;
                }

                while (extension != NULL)
                {
                    if (DGifGetExtensionNext(ptData->gpGifFile, &extension) == GIF_ERROR)
                    {
                        ret = -5;
                        break;
                    }
                }
                break;

            case TERMINATE_RECORD_TYPE:
                break;

            default:
                break;
        }

        if (0 < ret)
        {
            break;
        }
    } while (gRecordType != TERMINATE_RECORD_TYPE);

    return ret;
}

/**********************************************************************
 * 函数名称： ReleaseGif2Rgb
 * 功能描述：释放解析及使用PNG时使用的内存
 * 输入参数： ptPicData - 内含文件信息
 * 返 回 值：无
 ***********************************************************************/
static void ReleaseGif2Rgb(PT_PictureData ptPicData){
	GifFreeFile(ptPicData);
	free(ptPicData->pucRgbData);
}

int main(int argc,char *argv[])
{
    T_PictureData ptData;
    int ret;

    ret = FBDeviceInit();

	ret = DecodeGif2Rgb(argv[1], &ptData);
	
	ret = GifFrameShow(&ptData);

    ReleaseGif2Rgb(&ptData);

    return 0;
}


/**********************************************************************
 * 函数名称： FBDeviceInit
 * 功能描述： "framebuffer显示设备"的初始化函数
 * 输入参数： 无
 * 输出参数： 无
 * 返 回 值： 0 - 成功, 其他值 - 失败
 ***********************************************************************/
static int FBDeviceInit(void)
{
	int ret;
	
	fd_fb = open(FB_DEVICE_NAME, O_RDWR);
	if (0 > fd_fb)
	{
		printf("can't open %s\n", FB_DEVICE_NAME);
	}

	ret = ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);
	if (ret < 0)
	{
		printf("can't get fb's var\n");
		return -1;
	}

	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;  //整屏全部像素点所占的字节数：800*480*32/8=1536000Byte = 1500KB ≈ 1.46MB
	fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fbmem == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

    line_width  = var.xres * var.bits_per_pixel / 8;  //每一行像素点所占据的字节数 800*32/8bit = 
	pixel_width = var.bits_per_pixel / 8;  //一个像素点的宽度 32/8bit = 4Byte
	
	return 0;
}

/**********************************************************************
 * 函数名称： lcd_put_pixel
 * 功能描述： 在LCD指定位置上输出指定颜色（描点）
 * 输入参数： x坐标，y坐标，颜色
 * 输出参数： 无
 * 返 回 值： 会
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned int *pen_32 = (unsigned int *)(fbmem+y*line_width+x*pixel_width);
	*pen_32 = color;
}

/**********************************************************************
 * 函数名称： FbShowPicture
 * 功能描述：将转换后的rgb数据显示到LCD上
 * 输入参数： ptData - 内含图像信息
 * 返 回 值： 无
 ***********************************************************************/
static void FbShowPicture(PT_PictureData ptData){
	int i ,j;
	int iColor;
	for(i = 0; i<ptData->iHeight; i++){
		for(j = 0; j<ptData->iWidth*3; j+=3){
			if(j/3<var.xres && i<var.yres){
				iColor = (ptData->pucRgbData[i*ptData->iWidth*3+j+0]<<16) + (ptData->pucRgbData[i*ptData->iWidth*3+j+1]<<8) + (ptData->pucRgbData[i*ptData->iWidth*3+j+2]<<0);
				lcd_put_pixel(j/3, i, iColor);
			}
		}
	}
}